import tkinter as tk
from tkinter import ttk, messagebox, font
import json
import time
import threading
import os
from typing import List, Dict
import ctypes
import platform

import easyocr
import numpy as np
from PIL import Image, ImageGrab
import pygetwindow as gw
import pydirectinput
import keyboard

# ================= 可选截图方式 =================
USE_MSS = False
try:
    if USE_MSS:
        import mss
        import mss.tools
except ImportError:
    USE_MSS = False


# ================= DPI / 屏幕工具函数 =================
def set_process_dpi_awareness():
    if platform.system() != "Windows":
        return
    try:
        ctypes.windll.shcore.SetProcessDpiAwareness(1)
    except Exception:
        try:
            ctypes.windll.user32.SetProcessDPIAware()
        except Exception:
            pass


def get_dpi_scaling_factor() -> float:
    if platform.system() != "Windows":
        return 1.0
    try:
        LOGPIXELSX = 88
        hwnd = ctypes.windll.user32.GetDesktopWindow()
        hdc = ctypes.windll.user32.GetWindowDC(hwnd)
        dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, LOGPIXELSX)
        ctypes.windll.user32.ReleaseDC(hwnd, hdc)
        return dpi / 96.0
    except Exception:
        return 1.0


def is_process_dpi_aware() -> bool:
    if platform.system() != "Windows":
        return True
    try:
        awareness = ctypes.c_int()
        ctypes.windll.shcore.GetProcessDpiAwareness(0, ctypes.byref(awareness))
        return awareness.value != -1
    except Exception:
        return True


set_process_dpi_awareness()
DPI_SCALING_FACTOR = get_dpi_scaling_factor()
PROCESS_DPI_AWARE = is_process_dpi_aware()


# ================= 日志模块 =================
class AppLogger:
    def __init__(self, log_dir="log"):
        self.log_dir = log_dir
        if not os.path.exists(self.log_dir):
            os.makedirs(self.log_dir)
        self.log_file = os.path.join(self.log_dir, f"log_{time.strftime('%Y-%m-%d')}.txt")

    def log(self, message: str):
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        full_message = f"[{timestamp}] {message}\n"
        print(full_message.strip())
        with open(self.log_file, "a", encoding="utf-8") as f:
            f.write(full_message)


# ================= OCR 引擎模块 =================
class OCREngine:
    _reader = None

    @classmethod
    def get_reader(cls, logger: AppLogger) -> easyocr.Reader:
        if cls._reader is None:
            logger.log("正在初始化 EasyOCR 引擎...")
            try:
                cls._reader = easyocr.Reader(['ch_sim', 'en'], gpu=True)
                logger.log("EasyOCR(GPU) 初始化成功。")
            except Exception:
                logger.log("GPU 初始化失败，尝试 CPU...")
                try:
                    cls._reader = easyocr.Reader(['ch_sim', 'en'], gpu=False)
                    logger.log("EasyOCR(CPU) 初始化成功。")
                except Exception as e_cpu:
                    logger.log(f"EasyOCR 初始化彻底失败: {e_cpu}")
                    messagebox.showerror("严重错误", "无法初始化 EasyOCR。")
                    raise e_cpu
        return cls._reader


# ================= 屏幕监控模块 =================
class ScreenMonitor:
    def __init__(self, app_controller, logger: AppLogger):
        self.app = app_controller
        self.logger = logger
        self.is_running = False
        self.monitor_thread = None
        self.last_hotkey = None

    def _capture_window_image(self, win):
        left, top, right, bottom = win.left, win.top, win.right, win.bottom
        width = right - left
        height = bottom - top
        if width <= 0 or height <= 0:
            raise ValueError("窗口尺寸无效")

        bbox = (left, top, right, bottom)
        self.logger.log(f"[Capture] 初次尝试 bbox={bbox} (w={width} h={height}) DPI_AWARE={PROCESS_DPI_AWARE} SCALE={DPI_SCALING_FACTOR:.2f}")

        if USE_MSS:
            with mss.mss() as sct:
                raw = sct.grab({"left": left, "top": top, "width": width, "height": height})
                img = Image.frombytes("RGB", raw.size, raw.rgb)
        else:
            img = ImageGrab.grab(bbox)

        if (img.width > width * 1.3 or img.height > height * 1.3) and PROCESS_DPI_AWARE:
            self.logger.log(f"[Capture] 警告：截图尺寸({img.width}x{img.height}) 大于窗口({width}x{height})，已忽略。")
        elif (img.width > width * 1.3 or img.height > height * 1.3) and not PROCESS_DPI_AWARE:
            scaled_bbox = (
                int(left * DPI_SCALING_FACTOR),
                int(top * DPI_SCALING_FACTOR),
                int(right * DPI_SCALING_FACTOR),
                int(bottom * DPI_SCALING_FACTOR)
            )
            self.logger.log(f"[Capture] 进程非 DPI Aware，尝试缩放 bbox -> {scaled_bbox}")
            if USE_MSS:
                sw = scaled_bbox[2] - scaled_bbox[0]
                sh = scaled_bbox[3] - scaled_bbox[1]
                with mss.mss() as sct:
                    raw = sct.grab({"left": scaled_bbox[0], "top": scaled_bbox[1], "width": sw, "height": sh})
                    img = Image.frombytes("RGB", raw.size, raw.rgb)
            else:
                img = ImageGrab.grab(scaled_bbox)
            bbox = scaled_bbox

        return img, bbox

    def _scan_and_click(self):
        target_window_title = self.app.get_target_window_title()
        if not target_window_title:
            self.logger.log("警告：未在 UI 中选择目标窗口。")
            return

        windows = gw.getWindowsWithTitle(target_window_title)
        if not windows:
            self.logger.log(f"警告：找不到标题为 '{target_window_title}' 的窗口，跳过本次扫描。")
            return
        win = windows[0]

        heroes = self.app.get_selected_heroes()
        if not heroes:
            self.logger.log("警告：未选择任何英雄。")
            return

        try:
            if win.isMinimized:
                win.restore()
            if gw.getActiveWindow() != win:
                win.activate()

            screenshot, bbox = self._capture_window_image(win)
            img_np = np.array(screenshot)

            crop_ratio = self.app.get_crop_ratio()
            y_offset = 0
            img_to_scan_np = img_np
            if 0 < crop_ratio < 1.0:
                original_height = img_np.shape[0]
                crop_height = int(original_height * crop_ratio)
                y_offset = original_height - crop_height
                img_to_scan_np = img_np[y_offset:, :]
                self.logger.log(f"已裁剪底部 {crop_ratio * 100:.1f}% 用于 OCR。")

            if self.app.is_debug_mode():
                debug_dir = self.app.get_debug_dir()
                os.makedirs(debug_dir, exist_ok=True)
                debug_name = os.path.join(debug_dir, f"debug_capture_{int(time.time())}.png")
                Image.fromarray(img_to_scan_np).save(debug_name)
                self.logger.log(f"调试模式：保存当前扫描图像 -> {debug_name}")

            reader = OCREngine.get_reader(self.logger)
            if not reader:
                self.logger.log("错误：OCR 引擎未就绪。")
                return

            ocr_start = time.perf_counter()
            ocr_results = reader.readtext(img_to_scan_np, detail=1, paragraph=False)
            duration = time.perf_counter() - ocr_start
            self.app.update_ocr_time(duration)
            self.logger.log(f"OCR 结果总数={len(ocr_results)} 耗时={duration:.4f}s")

            confidence_threshold = 0.3
            filtered = [r for r in ocr_results if r[2] >= confidence_threshold]

            for hero in heroes:
                for (box, text, prob) in filtered:
                    if hero in text:
                        center_x_local = (box[0][0] + box[1][0]) / 2
                        center_y_local_cropped = (box[0][1] + box[2][1]) / 2
                        abs_x = bbox[0] + center_x_local
                        abs_y = bbox[1] + y_offset + center_y_local_cropped
                        self.logger.log(f"找到 '{hero}' 置信度={prob:.2f} 点击 ({int(abs_x)},{int(abs_y)})")
                        pydirectinput.moveTo(int(abs_x), int(abs_y))
                        pydirectinput.click()
                        return
            self.logger.log("本轮未匹配到任何目标英雄。")
        except Exception as e:
            self.logger.log(f"扫描时发生异常: {e}")

    def _periodic_scan_loop(self):
        while self.is_running:
            interval = self.app.get_scan_interval()
            self._scan_and_click()
            time.sleep(interval)

    def start_periodic_monitoring(self):
        if self.is_running:
            self.logger.log("周期监控已在运行。")
            return
        self.is_running = True
        self.monitor_thread = threading.Thread(target=self._periodic_scan_loop, daemon=True)
        self.monitor_thread.start()
        self.logger.log("周期监控已启动。")
        self.app.update_scan_status()

    def stop_monitoring(self):
        if self.is_running:
            self.is_running = False
            if self.monitor_thread:
                self.monitor_thread.join(timeout=1)
            self.logger.log("监控已停止。")
            self.app.update_scan_status()

    def toggle_periodic_monitoring(self):
        if self.is_running:
            self.stop_monitoring()
        else:
            self.start_periodic_monitoring()

    def setup_hotkey(self):
        if self.last_hotkey:
            try:
                keyboard.remove_hotkey(self.last_hotkey)
            except KeyError:
                pass
        try:
            hotkey = self.app.get_hotkey()
            if hotkey:
                keyboard.add_hotkey(hotkey, self._scan_and_click)
                self.last_hotkey = hotkey
                self.logger.log(f"单次扫描热键 '{hotkey}' 已设置。")
        except Exception as e:
            self.logger.log(f"设置热键失败: {e}")


# ================= GUI 主程序 =================
class HeroMonitorApp(tk.Tk):
    HERO_BUTTONS_PER_ROW = 10

    def __init__(self):
        super().__init__()
        self.title("TFT拿牌器(Home 放大缩小 / Pause 控制周期扫描开启和关闭 / End 退出)  如有问题请加群：752466695")

        self.logger = AppLogger()
        self.logger.log(f"进程 DPI_AWARE={PROCESS_DPI_AWARE} 系统缩放={DPI_SCALING_FACTOR:.2f}")

        default_font = font.nametofont("TkDefaultFont")
        default_font.configure(family="Microsoft YaHei", size=9)
        self.option_add("*Font", default_font)

        # 数据
        self.origin_traits = ["奥德赛", "水晶玫瑰", "星之守护者", "超级战队", "斗魂战士",
                              "战斗学院", "至高天", "假面摔角手", "兵王", "大宗师"]
        self.class_traits = ["护卫", "决斗大师", "裁决使者", "刀锋领主", "重量级斗士",
                             "主宰", "天才", "圣盾使", "法师", "司令"]

        self.all_heroes_data = self._load_champions()
        self.monitor = ScreenMonitor(self, self.logger)
        self.selected_heroes: List[str] = []
        self.active_filters = {'cost': None, 'trait': None}

        # 状态显示
        self.scan_status_var = tk.StringVar(value="周期扫描: 已停止")
        self.status_window = None
        self.status_label = None
        self.showing_status_window = False

        self.debug_dir = "debug_images"
        self.current_unselected_displayed: List[str] = []

        self._create_widgets()
        self.update_hero_displays()
        self._setup_hotkeys()
        self.update_scan_status()

        threading.Thread(target=OCREngine.get_reader, args=(self.logger,), daemon=True).start()

        self.protocol("WM_DELETE_WINDOW", self._on_closing)
        self.resizable(False, False)

    # ====== 状态窗口 ======
    def _create_status_window(self):
        if self.status_window and tk.Toplevel.winfo_exists(self.status_window):
            return
        self.status_window = tk.Toplevel(self)
        self.status_window.title("扫描状态")
        self.status_window.resizable(False, False)
        self.status_window.attributes("-topmost", True)
        frame = ttk.Frame(self.status_window, padding=15)
        frame.pack(fill="both", expand=True)
        self.status_label = ttk.Label(frame, text=self.scan_status_var.get(), font=("Microsoft YaHei", 14, "bold"))
        self.status_label.pack(padx=5, pady=(0, 10))
        ttk.Label(
            frame,
            text="Home 切换主界面/状态窗\nPause 启动/停止周期扫描\nD 单次扫描\nEnd 退出程序",
            justify="center"
        ).pack()
        self.status_window.protocol("WM_DELETE_WINDOW", self._on_status_window_close)
        self.status_window.update_idletasks()
        w = self.status_window.winfo_width()
        h = self.status_window.winfo_height()
        sw = self.status_window.winfo_screenwidth()
        sh = self.status_window.winfo_screenheight()
        self.status_window.geometry(f"+{sw - w - 60}+{sh - h - 120}")

    def _on_status_window_close(self):
        if self.status_window:
            self.status_window.withdraw()
        if self.showing_status_window:
            self.deiconify()
            self.showing_status_window = False

    def toggle_status_view(self):
        if self.showing_status_window:
            if self.status_window and tk.Toplevel.winfo_exists(self.status_window):
                self.status_window.withdraw()
            self.deiconify()
            self.showing_status_window = False
            self.logger.log("切换到主窗口视图。")
        else:
            self._create_status_window()
            self.update_scan_status()
            self.withdraw()
            self.status_window.deiconify()
            self.showing_status_window = True
            self.logger.log("切换到状态小窗口视图。")

    def update_scan_status(self):
        text = "周期扫描: 运行中" if self.monitor.is_running else "周期扫描: 已停止"
        self.scan_status_var.set(text)
        if self.status_label:
            self.status_label.config(text=self.scan_status_var.get())

    # ====== 关闭 / 退出 ======
    def _on_closing(self):
        if self.monitor.is_running:
            self.monitor.stop_monitoring()
        try:
            if self.status_window and tk.Toplevel.winfo_exists(self.status_window):
                self.status_window.destroy()
        except Exception:
            pass
        self.logger.log("程序退出。")
        self.destroy()

    def exit_app(self):
        # End 键调用
        self._on_closing()

    # ====== 数据加载 ======
    def _load_champions(self) -> List[Dict]:
        try:
            with open("champions.json", "r", encoding="utf-8") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError) as e:
            messagebox.showerror("错误", f"无法加载 champions.json: {e}")
            self.destroy()
            return []

    # ====== 热键 ======
    def _setup_hotkeys(self):
        # Home: 主界面 / 状态窗 切换
        keyboard.add_hotkey('home', self.toggle_status_view)
        # Pause: 启动/停止周期扫描
        keyboard.add_hotkey('pause', self.monitor.toggle_periodic_monitoring)
        # End: 退出程序
        keyboard.add_hotkey('end', self.exit_app)
        self.logger.log("已绑定 Home(切换视图) Pause(启动/停止周期扫描) End(退出程序)。")
        # 单次扫描键（默认 D）已在 setup_hotkey 中注册
        self.monitor.setup_hotkey()

    # ====== UI 生成辅助 ======
    def _create_wrapping_buttons(self, parent, label_text, button_list, command_lambda, buttons_per_row=10):
        frame = ttk.Frame(parent)
        frame.pack(fill="x", pady=2)
        ttk.Label(frame, text=f"{label_text}:", width=10).pack(side="left", anchor='n')
        container = ttk.Frame(frame)
        container.pack(side="left", fill="x", expand=True)
        for i, item in enumerate(button_list):
            row, col = divmod(i, buttons_per_row)
            ttk.Button(container, text=item, command=lambda t=item: command_lambda(t)).grid(
                row=row, column=col, padx=1, pady=1, sticky="ew"
            )

    def _create_widgets(self):
        main_frame = ttk.Frame(self, padding=5)
        main_frame.pack(fill="both", expand=True)

        # 顶部控制
        top_frame = ttk.Frame(main_frame)
        top_frame.pack(fill="x")

        control_frame = ttk.LabelFrame(top_frame, text="控制面板")
        control_frame.pack(side="left", fill="x", expand=True, padx=(0, 5))

        ttk.Label(control_frame, text="目标窗口:").pack(side="left", padx=5)
        self.window_var = tk.StringVar()
        self.window_menu = ttk.OptionMenu(control_frame, self.window_var, "未选择")
        self.window_menu.pack(side="left", padx=5, fill="x", expand=True)

        ttk.Button(control_frame, text="刷新", command=self._update_window_list, width=6).pack(side="left", padx=5)
        self.start_button = ttk.Button(control_frame, text="启动", command=self.monitor.start_periodic_monitoring, width=6)
        self.start_button.pack(side="right", padx=2)
        self.stop_button = ttk.Button(control_frame, text="停止", command=self.monitor.stop_monitoring, width=6)
        self.stop_button.pack(side="right", padx=2)

        window_settings_frame = ttk.LabelFrame(top_frame, text="窗口设置")
        window_settings_frame.pack(side="left")

        self.always_on_top_var = tk.BooleanVar()
        ttk.Checkbutton(window_settings_frame, text="置顶", variable=self.always_on_top_var,
                        command=self.toggle_always_on_top).pack(side="left")

        alpha_frame = ttk.Frame(window_settings_frame)
        alpha_frame.pack(side="left", padx=5)
        ttk.Label(alpha_frame, text="透明:").pack(side="left")
        for val in [100, 80, 60, 40]:
            ttk.Button(alpha_frame, text=f"{val}%", command=lambda v=val: self.set_alpha(v / 100.0), width=5).pack(side="left")

        # 参数设置
        settings_frame = ttk.LabelFrame(main_frame, text="参数设置")
        settings_frame.pack(fill="x", pady=5)

        ttk.Label(settings_frame, text="单次扫描键:").pack(side="left", padx=5)
        self.hotkey_var = tk.StringVar(value="d")
        ttk.Entry(settings_frame, textvariable=self.hotkey_var, width=8).pack(side="left")
        ttk.Button(settings_frame, text="设置", command=self.monitor.setup_hotkey, width=5).pack(side="left", padx=5)

        ttk.Label(settings_frame, text="扫描间隔(秒):").pack(side="left", padx=5)
        self.interval_var = tk.DoubleVar(value=0.15)
        ttk.Entry(settings_frame, textvariable=self.interval_var, width=5).pack(side="left")

        ttk.Label(settings_frame, text="底部保留比例(%):").pack(side="left", padx=5)
        self.crop_ratio_var = tk.DoubleVar(value=5.0)
        ttk.Entry(settings_frame, textvariable=self.crop_ratio_var, width=5).pack(side="left")

        self.debug_mode_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(settings_frame, text="开启截图调试", variable=self.debug_mode_var).pack(side="left", padx=10)

        ttk.Label(settings_frame, textvariable=self.scan_status_var, foreground="green").pack(side="left", padx=15)

        self.ocr_time_label = ttk.Label(settings_frame, text="OCR 耗时: N/A")
        self.ocr_time_label.pack(side="right", padx=10)

        # 筛选区
        filter_frame = ttk.LabelFrame(main_frame, text="英雄筛选")
        filter_frame.pack(fill="x", pady=5)

        reset_filter_btn = ttk.Button(filter_frame, text="重置筛选", command=self.reset_filters)
        reset_filter_btn.pack(side="right", padx=5, pady=2)

        self._create_wrapping_buttons(filter_frame, "费用", [f"{c}费" for c in range(1, 6)], self.apply_cost_filter)
        self._create_wrapping_buttons(filter_frame, "特性", self.origin_traits, self.apply_trait_filter)
        self._create_wrapping_buttons(filter_frame, "职业", self.class_traits, self.apply_trait_filter)

        self.unselected_frame = ttk.LabelFrame(main_frame, text="未选区 (点击添加)")
        self.unselected_frame.pack(fill="x", pady=(5, 2))
        self.selected_frame = ttk.LabelFrame(main_frame, text="已选区 (点击移除)")
        self.selected_frame.pack(fill="x", pady=(2, 5))

    # ====== 筛选 & 批量操作 ======
    def reset_filters(self):
        self.active_filters['cost'] = None
        self.active_filters['trait'] = None
        self.logger.log("已重置筛选条件。")
        self.update_hero_displays()

    def add_all_unselected(self):
        if not self.current_unselected_displayed:
            self.logger.log("当前无可添加的未选英雄。")
            return
        added = 0
        for name in self.current_unselected_displayed:
            if name not in self.selected_heroes:
                self.selected_heroes.append(name)
                added += 1
        self.logger.log(f"批量添加未选英雄数量: {added}")
        self.update_hero_displays()

    def remove_all_selected(self):
        count = len(self.selected_heroes)
        if count == 0:
            self.logger.log("当前已选区为空，无需移除。")
            return
        self.selected_heroes.clear()
        self.logger.log(f"已移除全部已选英雄，共 {count} 个。")
        self.update_hero_displays()

    def apply_cost_filter(self, value: str):
        digits = ''.join(ch for ch in value if ch.isdigit())
        if digits:
            self.active_filters['cost'] = int(digits)
            self.logger.log(f"设置费用筛选 = {self.active_filters['cost']}")
        self.update_hero_displays()

    def apply_trait_filter(self, value: str):
        self.active_filters['trait'] = value
        self.logger.log(f"设置特性/职业筛选 = {value}")
        self.update_hero_displays()

    def update_hero_displays(self):
        for frame in [self.unselected_frame, self.selected_frame]:
            for widget in frame.winfo_children():
                widget.destroy()

        filtered = self.all_heroes_data
        if self.active_filters['cost'] is not None:
            filtered = [h for h in filtered if h['费用'] == self.active_filters['cost']]
        if self.active_filters['trait'] is not None:
            filtered = [h for h in filtered if self.active_filters['trait'] in h['职业/特性']]

        unselected = [h for h in filtered if h['英雄'] not in self.selected_heroes]
        selected = [h for h in self.all_heroes_data if h['英雄'] in self.selected_heroes]

        self.current_unselected_displayed = [h['英雄'] for h in unselected]

        unselected_top = ttk.Frame(self.unselected_frame)
        unselected_top.pack(fill="x", pady=(2, 2))
        ttk.Label(unselected_top, text=f"未选数量: {len(unselected)}").pack(side="left", padx=5)
        ttk.Button(unselected_top, text="全部添加(当前筛选)", command=self.add_all_unselected).pack(side="right", padx=5)

        selected_top = ttk.Frame(self.selected_frame)
        selected_top.pack(fill="x", pady=(2, 2))
        ttk.Label(selected_top, text=f"已选数量: {len(selected)}").pack(side="left", padx=5)
        ttk.Button(selected_top, text="全部移除", command=self.remove_all_selected).pack(side="right", padx=5)

        unselected_container = ttk.Frame(self.unselected_frame)
        unselected_container.pack(fill="x")
        selected_container = ttk.Frame(self.selected_frame)
        selected_container.pack(fill="x")

        for i, hero in enumerate(sorted(unselected, key=lambda x: (x['费用'], x['英雄']))):
            row, col = divmod(i, self.HERO_BUTTONS_PER_ROW)
            ttk.Button(unselected_container,
                       text=f"{hero['英雄']}({hero['费用']})",
                       command=lambda n=hero['英雄']: self.toggle_hero_selection(n)
                       ).grid(row=row, column=col, padx=1, pady=1, sticky="ew")

        for i, hero in enumerate(sorted(selected, key=lambda x: (x['费用'], x['英雄']))):
            row, col = divmod(i, self.HERO_BUTTONS_PER_ROW)
            ttk.Button(selected_container,
                       text=f"{hero['英雄']}({hero['费用']})",
                       command=lambda n=hero['英雄']: self.toggle_hero_selection(n)
                       ).grid(row=row, column=col, padx=1, pady=1, sticky="ew")

    def toggle_hero_selection(self, hero_name):
        if hero_name in self.selected_heroes:
            self.selected_heroes.remove(hero_name)
            self.logger.log(f"移除 '{hero_name}' 从选择区。")
        else:
            self.selected_heroes.append(hero_name)
            self.logger.log(f"添加 '{hero_name}' 到选择区。")
        self.update_hero_displays()

    # ====== 窗口控制 ======
    def toggle_always_on_top(self):
        is_on_top = self.always_on_top_var.get()
        self.attributes("-topmost", is_on_top)
        if self.status_window and tk.Toplevel.winfo_exists(self.status_window):
            self.status_window.attributes("-topmost", is_on_top)
        self.logger.log(f"窗口置顶 {'开启' if is_on_top else '关闭'}")

    def set_alpha(self, value: float):
        self.attributes("-alpha", float(value))
        self.logger.log(f"窗口透明度设置为 {int(value * 100)}%")

    def _update_window_list(self):
        menu = self.window_menu["menu"]
        menu.delete(0, "end")
        windows = [w.title for w in gw.getAllWindows() if w.title.strip()]
        if not windows:
            menu.add_command(label="未找到任何窗口", state="disabled")
            self.window_var.set("未选择")
            return
        current = self.window_var.get()
        for title in windows:
            menu.add_command(label=title, command=lambda t=title: self.window_var.set(t))
        if current not in windows:
            self.window_var.set(windows[0])

    # ====== 对监控模块接口 ======
    def get_target_window_title(self) -> str:
        title = self.window_var.get()
        return title if title and title != "未选择" else None

    def get_selected_heroes(self) -> List[str]:
        return self.selected_heroes

    def get_hotkey(self) -> str:
        return self.hotkey_var.get().strip().lower()

    def get_scan_interval(self) -> float:
        try:
            return max(0.05, float(self.interval_var.get()))
        except Exception:
            return 0.1

    def get_crop_ratio(self) -> float:
        try:
            return max(0.0, min(100.0, float(self.crop_ratio_var.get()))) / 100.0
        except Exception:
            return 0.05

    def is_debug_mode(self) -> bool:
        return self.debug_mode_var.get()

    def get_debug_dir(self) -> str:
        return self.debug_dir

    def update_ocr_time(self, duration: float):
        self.after(0, lambda: self.ocr_time_label.config(text=f"OCR 耗时: {duration:.4f}s"))


# ================= 程序入口 =================
if __name__ == "__main__":
    app = HeroMonitorApp()
    app.mainloop()